package network;

import java.io.IOException;
import java.io.PrintStream;

import core.Phrox;

public class Old_ServerThread extends Thread{

	//public final int MAX_CONNECTIONS = 10;


	public Old_Server serv = null;
	protected Connection[] connectionList = new Connection[NetworkVars.MAX_CONNECTIONS];
	private boolean hasConnection = true;
	private Boolean isAdding = false;//so we create thread, and we don't remove it if we are in action of adding a connection
	
	public Old_ServerThread(Old_Server s){
		serv = s;
		System.out.println("NEW THREAD");

	}


	/**
	 * 
	 * @param c Connection to add to list.
	 * @return returns an integer depending on what happens.
	 * 1: connection connected. 
	 * -1: couldn't connect. dropped.
	 * 3: can't hold another connection. Create new server thread
	 */
	public int addConnection(Connection c){
		synchronized(isAdding){
			isAdding = true;
			if(isFull()){
				return 3;
			}

			if(!connect(c)){//if we can't connect
				return -1;
			}

			if(serv.isBanned(c.socket.getInetAddress())){
				c.addMsg(Old_Server.BAN_MSG);
				disconnect(c);
				return -1;
			}

			for(int i=0; i<NetworkVars.MAX_CONNECTIONS; i++){
				//we connected, add this to the list of connections, at the first null
				if(connectionList[i] == null){
					connectionList[i] = c;

					break;
				}
			}
			hasConnection = !isEmpty();
			isAdding=false;
			c.localDisconnect = false;//assume we disconnect.
			joined(c);

			return 0;//return connected
		}
	}

	/**
	 * 
	 * @param c the connection to remove from the list
	 * @return returns true if the connection was removed.
	 */
	public boolean removeConnection(Connection c){
		boolean removed = false;
		serv.removeUser(c.user);

		for(int i=0; i< connectionList.length;i++){

			if(c.equals(connectionList[i])){
				removed = true;
				try {
					disconnect(c,true);
				} catch (Exception e) {//catch the exceptions.
				}
				c.close();//free resources
				connectionList[i] = null;
				break;
			}

		}

		return removed;
	}


	public void run(){
		while(hasConnection){//run will terminate when the thread has no more connections.

			for(int i=0; i<NetworkVars.MAX_CONNECTIONS; i++){

				try {
					if(connectionList[i] == null || connectionList[i].connectionStatus != 1){//empty.
						continue;
					}

					Connection c = connectionList[i];

					Message m = null;
					if(c.msgIn.ready()){
						
						c.lastAlive = System.currentTimeMillis();
						
						String in = c.msgIn.readMessage();
						m = Message.parseMessage(in);


						if(c.encP != null){
							m.setText( c.encP.decrypt(m.getText()));
						}

						Message send = null;//send to everyone
						String name = c.user.getName();
						if(name.equals("")){
							name= c.user.getIp().toString();
						}
						String tmp = "";
						switch(m.getType()){
						case Message.TEXT_MSG://msgType.TEXT_MSG
							send = new Message(Message.TEXT_MSG, m.getText());
							break;

						case Message.CHANGE_NAME://Message.CHANGE_NAME:
							send = new Message(Message.TEXT_MSG, ""+name+" is now known as "+m.getText());//TODO format message
							c.user.setName(m.getText());
							break;

						case Message.REQUEST_MSG://Message.REQUEST_MSG:
							tmp = clientRequest(m.getText());
							if(tmp.length() > 1){
								Message req = new Message(Message.TEXT_MSG,tmp);//TODO format message
								send(c, req);
							}
							break;

						case Message.PINGS://Message.PINGC
							//for a ping the contents are the time it was sent, so we just send it back.
							// so that the computer on the other end knows the time lapse.
							send(c, m);
							System.out.println("Pinged by:"+c.user.getName());
							break;
							
						case Message.PINGR://msgType.PINGS
							long start = Long.parseLong(m.getText());
							long end = System.currentTimeMillis();
							double lapse = (end - start)/1.0;
							tmp = "Lag from "+c.user.getIp()+":"+c.user.getName()+" is "+lapse+"ms";
							System.out.println(tmp);
							Phrox.outln(tmp);
							break;
						case Message.FILE_FRAGMENT://Message.FILE_FRAGMENT:
							//handle this later
							break;

						case Message.CONNECTION_MSG://Message.CONNECTION_MSG:
							c.flagDisconnect=true;
							c.localDisconnect = true;//
							break;
						case Message.KEEP_ALIVE:
							send(c,new Message(Message.KEEP_ALIVE, ""));
							break;
						default:
						case Message.UNDEFINED_TYPE://Message.UNDEFINED_TYPE:
							//print out log that we got the message, the type (#) and text.
							System.out.println("Unknown Msg Type:"+m.getType()+"-"+m.getText()+"");

							break;
						}

						if(send != null){
							serv.sendAll(send);
						}
					}
					
					if( core.Util.timeLapsed(System.currentTimeMillis(), c.lastAlive, NetworkVars.connection_Timeout )){
						//assume they 
						c.flagDisconnect = true;
						if(NetworkVars.debug){
							System.out.println("Disconnecting:"+c.user.getName()+" due to timeout");
						}
						dropConnection(c);
						serv.remove(c);
						serv.sendAll(new Message(Message.TEXT_MSG, ""+c.user.getName()+" timed out..."));//TODO format message
						continue;//service next connection
					}

					int numSend = c.numSend();
					if(numSend > 0){//if we need to send this connection messages...

						for(Message msgSend : c.getMsgList()){
							if(msgSend != null){
								write(c,msgSend);
								msgSend = null;//remove the sent message from the list
							}
						}
					}
					
					if(c.flagDisconnect || serv.close){
						try {
							disconnect(c, c.localDisconnect && !serv.close);
							serv.remove(c);
						} catch (Exception e) {
							e.printStackTrace();
						}
					}

	
				} catch (IOException e) {
					e.printStackTrace();
				} catch (MessageFormatException e) {
					e.printStackTrace();
				} catch (DisconnectionException e) {
					//this connection needs to be removed.
					e.printStackTrace();
				} catch(Exception e){
					//should remove connection?
					e.printStackTrace();
				}

				try {
					Thread.sleep(NetworkVars.defSleep/10);
				} catch (InterruptedException e) {}

			}

			try {
				Thread.sleep(NetworkVars.defSleep);
			} catch (InterruptedException e) {}



			hasConnection = !isEmpty() || isAdding;

		}//end of hasConnection loop

		//this thread no longer has any connections.

		serv.removeThread(this);
		System.out.println("THREAD CLOSED");

	}


	/**
	 * 
	 * @return returns true if this can not handle any more connections.
	 */
	public boolean isFull(){
		return getEmpty() == 0;//no empty connections = full
	}

	/**
	 * 
	 * @return returns the number of empty connections for this thread
	 */
	public int getEmpty(){
		int empty = 0;
		for(Connection c: connectionList){
			if(c == null){
				empty++;
			}
		}
		return empty;
	}

	public boolean isEmpty(){
		return getEmpty() == NetworkVars.MAX_CONNECTIONS;
	}


	/**
	 * 
	 * @param m Message to queue for sending.
	 */
	public void sendAll(Message m){
		for(Connection c : connectionList){
			if(c != null){
				try {
					send(c,m);
				} catch (DisconnectionException e) {
					disconnect(c);
					//e.printStackTrace();
				}
			}
		}
		//core.Phrox.outln(m.getText());
	} 

	/**
	 * @param m Message to queue for sending
	 * @throws DisconnectionException 
	 */
	public static void send(Connection c, Message m) throws DisconnectionException{
		if(c.connectionStatus != 1){
			throw new DisconnectionException();
		}
		c.addMsg(m);
	}


	public void joined(Connection c){
		Message m = new Message(Message.TEXT_MSG, ""+c.user.getIp()+" has joined...");//TODO format message
		serv.sendAll(m);
	}


	/**
	 * 
	 * @param c connection to connect.
	 * @return returns true if the connection connected, false if there was a problem
	 */
	public boolean connect(Connection c) {
		synchronized(c){
			try {
				c.connectionStatus=1;
				//set up in and out
				c.stdOut = new PrintStream(c.socket.getOutputStream(), true);
				c.msgIn = new MessageReader(c.socket.getInputStream());
				//start setting up for connection
				//String in="",out="";
				Message in=new Message(""), out;
					out = new Message(Message.CONNECTION_MSG,NetworkingProtocol.CONNECT_REQ);
					while (!in.getText().equals("CONNECTED")) {
						write(c,out);

						in = read(c);

						if(in.getText().equals("CONNECTED")){
							break;
						}

						out.setText( c.netProt.protocol(in.getText()) );


						if (out.getText() != null && out.getText().equals("ERROR")) {
							//we've already sent the error message to the client.
							//the client will handle it on there side.
							//then disconnect and terminate this thread.
							c.stdOut.close();
							System.out.println("Disconnecting: "
									+ c.socket.getInetAddress().toString());
							return false;//this should terminate the thread properly.
						}

					}
				//core.Phrox.outln("Connect: "+c.socket.getInetAddress().toString());
				System.out.println("Connecting: "+c.socket.getInetAddress().toString());
				//now we're connected			

				c.user = new User(c.socket.getInetAddress());//set up username here.

				if(!serv.addUser(c.user)){
					//write(c,"You have been banned.");
					write(c,Old_Server.BAN_MSG);
					disconnect(c,false);
					return false;
				}

				c.msgIn = new MessageReader(c.socket.getInputStream());


			} catch (Exception e) {
				e.printStackTrace();
				c.close();
				return false;
			}
		}
		c.aliveNow();
		return true;
	}


	public boolean disconnect(Connection c, boolean b) throws IOException, DisconnectionException {
		synchronized(c){
			if(c.connectionStatus != 1){
				throw new DisconnectionException("Already Disconnect(ing/ed)");
			}
			c.connectionStatus = 0;
				//String in = NetworkingProtocol.DISCONNECT, out = "";
				Message in = new Message(Message.CONNECTION_MSG,NetworkingProtocol.DISCONNECT)
				, out=new Message(Message.CONNECTION_MSG,NetworkingProtocol.DISCONNECT);
				if (b) {
					//client started it
					//we already read the clients first msg
					out.setText( c.netProt.protocol(in.getText()) );//put this here to avoid an if statement in the loop
				}
					while(!out.getText().equals( NetworkingProtocol.END )){
						write(c,out);
						do{
							in = read(c);//drop all messages that arn't connection msgs.
						}while(in.getType() != Message.CONNECTION_MSG);

						out.setText( c.netProt.protocol(in.getText()) );
					}
					write(c,out);//write the final "Goodbye"
				
		}
		System.out.println("Disconnected:"+c.socket.getInetAddress().getHostAddress());
		c.close();
		return true;
	}

	public static boolean dropConnection(Connection c) throws DisconnectionException{
		synchronized(c){
			if(c.connectionStatus != 1){
				throw new DisconnectionException("Already Disconnect(ing/ed)");
			}
			c.connectionStatus = 0;
			
			c.netProt.reset();//not that this matters, as this won't be used again.
			
			c.connectionStatus = -1;
			System.out.println("Disconnected:"+c.socket.getInetAddress().getHostAddress());
			c.close();
		}
		return true;
	}
	
	public static void disconnect(Connection c){
		c.flagDisconnect = true;
	}


	public static void write(Connection c, Message m) throws DisconnectionException{
		synchronized(c){
			if(c.connectionStatus == -1){
				throw new DisconnectionException();
			}
			if(NetworkVars.showData){
				System.out.println("OUT: "+m.getText());
			}
			if(c.encP != null){//encrypt only the contents of the msg.
				m.setText( c.encP.encrypt(m.getText()));
			}
			if(NetworkVars.showData){
				System.out.println("OUT: "+m.getText());
			}
			c.stdOut.print(""+Message.convertMessage(m)+""+((char)Message.DELIMITER));

		}
	}

	public static Message read(Connection c) throws DisconnectionException, IOException{
		if(c.connectionStatus == -1){
			throw new DisconnectionException();
		}
		Message ret = null;
		while(!c.msgIn.ready());
		String in = c.msgIn.readMessage();
		try {
			ret = Message.parseMessage(in);
		} catch (MessageFormatException e) {}
		if(NetworkVars.showData){
			System.out.println("IN: "+ret.getText());
		}
		if(c.encP != null){
			ret.setText( c.encP.decrypt(ret.getText()) );
		}
		if(NetworkVars.showData){
			System.out.println("IN: "+ret.getText());
		}

		return ret;
	}



	public String clientRequest(String str){
		str = str.toLowerCase().substring(str.indexOf("/")).trim();
		String ret ="";
		if(str.equals("/members")){
			ret = serv.getUserList();
		}
		return ret;
	}


}
